package com.wanjia.estate.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfWriter;
import com.wanjia.estate.constant.DocumentType;
import com.wanjia.estate.constant.OcrType;
import com.wanjia.estate.exception.ControllerException;
import com.wanjia.estate.pojo.bdc.Head;
import com.wanjia.estate.pojo.bdc.registrat.RegistratRequest;
import com.wanjia.estate.pojo.bdc.registrat.RegistratResponse;
import com.wanjia.estate.pojo.bdc.spfhtcx.SpwqRequest;
import com.wanjia.estate.pojo.bdc.spfhtcx.SpwqResponse;
import com.wanjia.estate.pojo.document.InvoiceInfo;
import com.wanjia.estate.pojo.model.JsonResult;
import com.wanjia.estate.pojo.ocr.OcrResponse;
import com.wanjia.estate.properties.FolderPathProperties;
import com.wanjia.estate.properties.HttpRequestProperties;
import com.wanjia.estate.service.MoneyCacheService;
import com.wanjia.estate.service.httpservice.OcrHttpService;
import com.wanjia.estate.service.httpservice.PropertyHttpService;
import com.wanjia.estate.utils.NumberUtil;
import com.wanjia.estate.utils.ThreadUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;

@RestController
@RequestMapping("/document")
@Tag(name = "档案与识别控制器")
@Slf4j
public class DocumentController extends BaseController {

    @Autowired
    private FolderPathProperties folderPathProperties;

    @Autowired
    PropertyHttpService propertyHttpService;

/*    @Setter(onMethod_ = @Autowired)
    private RegistratHttpService registratHttpService;

    @Setter(onMethod_ = @Autowired)
    private SpfhtsjcxHttpService spfhtsjcxHttpService;*/

    @Setter(onMethod_ = @Autowired)
    private MoneyCacheService moneyCacheService;

    @Setter(onMethod_ = @Autowired)
    private HttpRequestProperties properties;

    @Setter(onMethod_ = @Autowired)
    private OcrHttpService ocrService;

    @Operation(summary = "文件保存接口")
    @PostMapping
    public JsonResult<Void> saveDocument(
            @Parameter(schema = @Schema(allowableValues = {
                    "contract", "invoice", "idCardIdentification", "jhz", "hkb",
                    "registrationCertificate", "supplemental", "consignment",
                    "printZydj", "printSjd", "printYdzfkplxd", "printZjsbdcjysbd", "printZlf","tc","cns","jmyds","jmcns"}))
            @RequestParam String type, HttpServletRequest request) {
        DocumentType documentType = DocumentType.get(type);
        if (documentType == null) {
            throw new ControllerException("不支持的类型: " + type);
        }
        synchronized (documentType.dir()) {
            bufferToDir(request, documentType.dir());
        }
        return new JsonResult<>(OK, null);
    }

    @Operation(summary = "文件清空接口")
    @DeleteMapping
    public JsonResult<Void> clearDocument(
            @Parameter(schema = @Schema(allowableValues = {
                    "contract", "invoice", "idCardIdentification", "jhz", "hkb",
                    "registrationCertificate", "supplemental", "consignment",
                    "printZydj", "printSjd", "printYdzfkplxd", "printZjsbdcjysbd", "printZlf","tc","cns","jmyds","jmcns"}))
            @RequestParam String type) {
        DocumentType documentType = DocumentType.get(type);
        if (documentType == null) {
            throw new ControllerException("不支持的类型: " + type);
        }
        synchronized (documentType.dir()) {
            clearDir(documentType.dir());
            if (Objects.equals(documentType, DocumentType.INVOICE)) {
                moneyCacheService.clear(ThreadUtil.getSlbh());
            }
        }
        return new JsonResult<>(OK, null);
    }

    @Operation(summary = "保存文件生成pdf接口")
    @PostMapping("/pdf")
    public JsonResult<Void> saveDocumentToPdf(
            @Parameter(schema = @Schema(allowableValues = {
                    "contract", "invoice", "idCardIdentification", "jhz", "hkb",
                    "registrationCertificate", "supplemental", "consignment",
                    "printZydj", "printSjd", "printYdzfkplxd", "printZjsbdcjysbd", "printZlf","tc","cns","jmyds","jmcns"}))
            @RequestParam String type) {
        DocumentType documentType = DocumentType.get(type);
        if (documentType == null) {
            throw new ControllerException("不支持的类型: " + type);
        }
        File dir = new File(folderPathProperties.getPdfpath() + File.separator + documentType.dir());

        synchronized (documentType.dir()) {
            checkImage(dir);
            File pdf = new File(folderPathProperties.getPdfpath() + File.separator + type + ".pdf");
            FileUtil.mkParentDirs(pdf);
            try (FileOutputStream out = new FileOutputStream(pdf, false)) {
                Document document = new Document();
                PdfWriter writer = PdfWriter.getInstance(document, out);
                document.open();
                writeImage(writer, document, dir);
                document.close();
            } catch (IOException | DocumentException e) {
                throw new ControllerException("pdf生成异常", e);
            }
        }

        return new JsonResult<>(OK, null);
    }

    private static void writeImage(PdfWriter writer, Document document, File dir) throws DocumentException, IOException {
        if (document == null) {
            return;
        }

        int index = 0;
        // 遍历检索文件名
        File img;
        while ((img = new File(dir, String.valueOf(index++))).exists()) {
            if (img.length() <= 0) {
                log.warn("{}号图片无效", index - 1);
                continue;
            }
            document.newPage();
            Image image = Image.getInstance(writer, ImgUtil.read(img), 0.5f);
            float height = image.getHeight();
            float width = image.getWidth();
            float percent = getPercent(height, width);
            image.setAlignment(Image.MIDDLE);
            // 表示是原来图像的比例
            image.scalePercent(percent);
            document.add(image);
        }
    }

    private static void checkImage(File dir) {
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            throw new ControllerException("无效目录");
        }

        int index = 0;
        int count = 0;
        // 遍历检索文件名
        File img;
        while ((img = new File(dir, String.valueOf(index++))).exists()) {
            if (img.length() <= 0) {
                continue;
            }
            count++;
        }

        if (count == 0) {
            throw new ControllerException("未找到有效图片");
        }
    }

    public static float getPercent(float h, float w) {
        return 530 / w * 100;
    }

    @Operation(summary = "合同识别获取信息接口")
    @GetMapping(value = "/contract")
    public JsonResult<SpwqResponse.ResponeseData> orcContract() {
        // 遍历调用OCR接口识别合同信息
        OcrType ot = OcrType.HT_01;
        DocumentType dt = ot.type();
        File dir = new File(folderPathProperties.getPdfpath() + File.separator + dt.dir());
        SpwqResponse.ResponeseData result;
        synchronized (dt.dir()) {
            result = orc(dir, ot.key()[0],(img, resp) -> {
                if (resp != null && !Objects.equals(resp.getCode(), 200)) {
                    log.error("文件[{}]识别异常: {}", img, JSONUtil.toJsonStr(resp));
                    return null;
                }

                List<OcrResponse.ResponseData> list = Optional.of(resp)
                        .map(OcrResponse::getData)
                        .orElseGet(ArrayList::new);
                String id;
                for (OcrResponse.ResponseData data : list) {
                    if (!Arrays.stream(ot.key()).anyMatch(key -> Objects.equals(key, data.getType()))) {
                        log.debug("文件[{}]识别类型不匹配: {}", img, data.getType());
                        continue;
                    }

                    id = data.getQrCode();
                    if (StrUtil.isNotBlank(id)) {
                        SpwqResponse.ResponeseData responeseData = query(id);
                        if (responeseData != null) {
                            return responeseData;
                        }
                    }

                    log.debug("扫码识别失败");
                    id = data.getOcr().getWqh();

                    if (StrUtil.isNotBlank(id)) {
                        SpwqResponse.ResponeseData responeseData = query(id);
                        if (responeseData != null) {
                            return responeseData;
                        }
                    }
                    log.debug("文字识别失败");
                }

                return null;
            });
        }
        if (result == null) {
            throw new ControllerException("识别失败");
        }

        return new JsonResult<>(OK, "Done", result);
    }

    private SpwqResponse.ResponeseData query(String coRegID) {
        Head head = new Head();
        head.setUsername(properties.getConfig().getUsername());
        head.setPassword(properties.getConfig().getPassword());
        SpwqRequest.RequestData data = new SpwqRequest.RequestData();
        data.setCoregid(coRegID);
        SpwqRequest spwqRequest = new SpwqRequest();
        spwqRequest.setHead(head);
        spwqRequest.setData(data);
        log.info("request: {}", JSONUtil.toJsonStr(spwqRequest));
        SpwqResponse response = propertyHttpService.Spfhtsjcx(spwqRequest);
        if (response == null || !Objects.equals(response.getCode(), "0000")) {
            throw new ControllerException("合同查询失败: " + coRegID);
        }
        return response.getData();
    }

    private <T> T orc(File dir,String type,BiFunction<File, OcrResponse, T> function) {
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            throw new ControllerException("无效目录");
        }

        int index = 0;
        // 遍历检索文件名
        File img;
        while ((img = new File(dir, String.valueOf(index++))).exists()) {
            if (img.length() <= 0) {
                continue;
            }

            try {
                OcrResponse response = ocrService.ocr(img,type);
                T result = function.apply(img, response);
                if (result != null) {
                    return result;
                }
            } catch (Exception e) {
                log.error("识别服务调用失败", e);
            }
        }

        return null;
    }

    @Operation(summary = "发票识别获取信息接口")
    @GetMapping(value = "/invoice")
    public JsonResult<JSONObject> orcInvoice(@RequestParam(required = true) @Parameter(description = "总金额") BigDecimal sum,
                                             @RequestParam(required = true) @Parameter(description = "误差阈值") BigDecimal error) {

        // 获取当前已识别图片下标
        String slbh = ThreadUtil.getSlbh();
        Integer index = moneyCacheService.getIndex(slbh);
        if (index == null || index < 0) {
            index = 0;
        }
        log.debug("查询当前下标: {}", index);

        // 获取已识别发票信息
        List<InvoiceInfo> infos = new ArrayList<>(moneyCacheService.getInfos(slbh));
        log.debug("查询当前已识别发票: {}", JSONUtil.toJsonStr(infos));
        AtomicReference<BigDecimal> tax = new AtomicReference<>(BigDecimal.ZERO);
        AtomicReference<BigDecimal> price = new AtomicReference<>(BigDecimal.ZERO);
        AtomicReference<BigDecimal> bhshj = new AtomicReference<>(BigDecimal.ZERO);

        // 计算求和
        Consumer<InvoiceInfo> consumer = info -> {
            try {
                tax.set(tax.get().add(new BigDecimal(info.getTax())));
            } catch (Exception e) {
                log.error("计算异常", e);
            }
            try {
                price.set(price.get().add(new BigDecimal(info.getPrice())));
            } catch (Exception e) {
                log.error("计算异常", e);
            }
            try {
                bhshj.set(bhshj.get().add(new BigDecimal(info.getBhshj())));
            } catch (Exception e) {
                log.error("计算异常", e);
            }
        };
        infos.forEach(consumer);

        // 遍历调用OCR接口识别发票信息
        OcrType ot = OcrType.GFFP;
        DocumentType dt = ot.type();
        synchronized (dt.dir()) {
            File dir = new File(folderPathProperties.getPdfpath() + File.separator + dt.dir());
            BiFunction<File, OcrResponse, InvoiceInfo> function = (img, resp) -> {
                if (resp != null && !Objects.equals(resp.getCode(), 200)) {
                    log.error("文件[{}]识别异常: {}", img, JSONUtil.toJsonStr(resp));
                    return null;
                }

                List<OcrResponse.ResponseData> list = Optional.of(resp)
                        .map(OcrResponse::getData)
                        .orElseGet(ArrayList::new);
                for (OcrResponse.ResponseData data : list) {
                    if (!Arrays.stream(ot.key()).anyMatch(key -> Objects.equals(key, data.getType()))) {
                        log.debug("文件[{}]识别类型不匹配: {}", img, data.getType());
                        continue;
                    }

                    InvoiceInfo ii = new InvoiceInfo();
                    OcrResponse.Ocr ocr = data.getOcr();
                    ii.setTax(ocr.getSe());
                    ii.setPrice(ocr.getJshz());
                    ii.setBhshj(ocr.getBhshj());
                    ii.setCode(ocr.getCode());
                    ii.setNo(ocr.getNo());
                    return ii;
                }

                return null;
            };

            if (dir == null || !dir.exists() || !dir.isDirectory()) {
                throw new ControllerException("无效目录");
            }

            // 遍历检索文件名
            File img;
            while ((img = new File(dir, String.valueOf(index))).exists()) {
                index++;
                if (img.length() <= 0) {
                    continue;
                }

                try {
                    OcrResponse response = ocrService.ocr(img,ot.key()[0]);
                    InvoiceInfo info = function.apply(img, response);
                    if (info != null) {
                        log.debug("新增识别发票: {}", JSONUtil.toJsonStr(info));
                        infos.add(info);
                        moneyCacheService.appendInvoiceInfo(slbh, info);
                        consumer.accept(info);
                    }
                } catch (Exception e) {
                    log.error("识别服务调用失败", e);
                }
            }
        }

        // 刷新缓存索引
        moneyCacheService.setIndex(slbh, index);
        log.debug("刷新缓存下标: {}", index);

        // 构造结果数据
        BigDecimal sumPrice = price.get();
        JSONObject data = new JSONObject();
        data.set("index", index);
        data.set("infos", infos);
        data.set("tax", NumberUtil.formatMoney(tax.get()));
        data.set("price",  NumberUtil.formatMoney(sumPrice));
        data.set("bhshj", NumberUtil.formatMoney( bhshj.get()));

        // 判断响应代码
        int status;
        BigDecimal min = sum.multiply(BigDecimal.ONE.subtract(error));
        BigDecimal max = sum.multiply(BigDecimal.ONE.add(error));
        if (sumPrice.compareTo(min) < 0) {
            status = 202;
        } else if (sumPrice.compareTo(max) <= 0) {
            status = sumPrice.compareTo(sum) == 0 ? 200 : 201;
        } else {
            status= 203;
        }
        data.set("status", status);

        return new JsonResult<>(OK, "Done", data);
    }

    @Operation(summary = "首次登记证明识别获取信息接口")
    @GetMapping(value = "/registrationCertificate")
    public JsonResult<RegistratResponse.ResponeseData> orcRegistrationCertificate() {
        // 遍历调用OCR接口首次登记证明信息
        OcrType ot = OcrType.SCDJZM;
        DocumentType dt = ot.type();
        File dir = new File(folderPathProperties.getPdfpath() + File.separator + dt.dir());
        RegistratResponse.ResponeseData responeseData;
        synchronized (dt.dir()) {
            responeseData = orc(dir,ot.key()[0], (img, resp) -> {
                log.debug("文件[{}]识别结果: {}", img, JSONUtil.toJsonStr(resp));

                if (resp != null && !Objects.equals(resp.getCode(), 200)) {
                    log.error("文件[{}]识别异常: {}", img, JSONUtil.toJsonStr(resp));
                    return null;
                }

                List<OcrResponse.ResponseData> list = Optional.of(resp)
                        .map(OcrResponse::getData)
                        .orElseGet(ArrayList::new);
                for (OcrResponse.ResponseData data : list) {
                    if (!Arrays.stream(ot.key()).anyMatch(key -> Objects.equals(key, data.getType()))) {
                        log.debug("文件[{}]识别类型不匹配: {}", img, data.getType());
                        continue;
                    }

                    if (StrUtil.isNotBlank(data.getQrCode())) {
                        RegistratRequest.RequestData rd = new RegistratRequest.RequestData();
                        rd.setBdcqzh(data.getQrCode());
                        RegistratResponse.ResponeseData responseData = queryRegistrationCertificate(rd);
                        if (responseData != null && CollUtil.isNotEmpty(responseData.getCqxx())) {
                            return responseData;
                        }
                    }

                    OcrResponse.Ocr ocr = data.getOcr();
                    if (StrUtil.isNotBlank(ocr.getBdcdyh())) {
                        RegistratRequest.RequestData rd = new RegistratRequest.RequestData();
                        rd.setBdcdyh(ocr.getBdcdyh());
                        RegistratResponse.ResponeseData responseData = queryRegistrationCertificate(rd);
                        if (responseData != null && CollUtil.isNotEmpty(responseData.getCqxx())) {
                            return responseData;
                        }
                    }

                    if (StrUtil.isNotBlank(ocr.getScdjzh())) {
                        RegistratRequest.RequestData rd = new RegistratRequest.RequestData();
                        rd.setBdcqzh(ocr.getScdjzh());
                        RegistratResponse.ResponeseData responseData = queryRegistrationCertificate(rd);
                        if (responseData != null && CollUtil.isNotEmpty(responseData.getCqxx())) {
                            return responseData;
                        }
                    }
                }

                return null;
            });
        }
        if (responeseData == null) {
            throw new ControllerException("识别失败");
        }

        return new JsonResult<>(OK, "Done", responeseData);
    }

    public RegistratResponse.ResponeseData queryRegistrationCertificate(RegistratRequest.RequestData data) {
        Head head = new Head();
        head.setUsername(properties.getConfig().getUsername());
        head.setPassword(properties.getConfig().getPassword());
        head.setRegioncode(properties.getConfig().getRegioncode());
        head.setOrgid(properties.getConfig().getOrgid());
        head.setSize(properties.getConfig().getSize());
        head.setPage(properties.getConfig().getPage());
        RegistratRequest registratRequest = new RegistratRequest();
        registratRequest.setHead(head);
        registratRequest.setData(data);
        RegistratResponse response = propertyHttpService.Registrat(registratRequest);
        if (response == null || !Objects.equals(response.getCode(), "0000")) {
            throw new ControllerException("合同查询失败: " + data);
        }
        return response.getData();
    }

    @Operation(summary = "通用识别校验接口")
    @GetMapping(value = "/{type}")
    public JsonResult<Integer> orc(@Parameter(in = ParameterIn.PATH,
            schema = @Schema(allowableValues = {"idCardIdentification", "supplemental", "consignment",
                    "printZydj", "printSjd", "printYdzfkplxd", "printZjsbdcjysbd", "printZlf", "jhz", "hkb",}))
                                   @PathVariable String type) {
        DocumentType dt = DocumentType.get(type);
        if (dt == null) {
            throw new ControllerException("不支持的类型: " + type);
        }

        OcrType ot = OcrType.get(dt);
        if (dt == null) {
            throw new ControllerException("不支持的识别类型: " + type);
        }

//        File dir = new File(folderPathProperties.getPdfpath() + File.separator + dt.dir());
//        AtomicInteger count = new AtomicInteger(0);
//        orc(dir, (img, resp) -> {
//            if (resp != null && !Objects.equals(resp.getCode(), 200)) {
//                log.error("文件[{}]识别异常: {}", img, JSONUtil.toJsonStr(resp));
//                return null;
//            }
//
//            List<OcrResponse.ResponseData> list = Optional.of(resp)
//                    .map(OcrResponse::getData)
//                    .orElseGet(ArrayList::new);
//            for (OcrResponse.ResponseData data : list) {
//                if (!Arrays.stream(ot.key()).anyMatch(key -> Objects.equals(key, data.getType()))) {
//                    log.debug("文件[{}]识别类型不匹配: {}", img, data.getType());
//                    continue;
//                }
//
//                count.incrementAndGet();
//                break;
//            }
//
//            return null;
//        });

        return new JsonResult<>(OK, 1);
    }

    private void clearDir(String dir) {
        String prefixFilePath = folderPathProperties.getPdfpath() + File.separator + dir + File.separator;

        File parent = new File(prefixFilePath);
        if (parent.exists()) {
            FileUtil.del(parent);
        }
    }

    private void bufferToDir(HttpServletRequest request, String dir) {
        // 生成路径
        String prefixFilePath = folderPathProperties.getPdfpath() + File.separator + dir + File.separator;
        log.debug("上级目录: {}", prefixFilePath);

        // 目录创建与文件命确定
        int index = 0;
        File parent = new File(prefixFilePath);
        if (!parent.exists()) {
            // 没有父目录时，直接创建
            FileUtil.mkdir(parent);
        } else {
            // 遍历检索文件名
            while (new File(prefixFilePath + index).exists()) {
                index++;
            }
        }
        File target = new File(prefixFilePath + index);
        log.debug("输出到文件: {}", target.getAbsolutePath());

        // 文件缓存
        InputStream in = null;
        try (OutputStream out = Files.newOutputStream(target.toPath())) {
            if (request instanceof MultipartHttpServletRequest) {
                in = ((MultipartRequest) request).getFile("file").getInputStream();
            } else {
                in = request.getInputStream();
            }
            IoUtil.copy(in, out);
        } catch (IOException e) {
            log.error("文件接收异常", e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.warn("流关闭异常", e);
                }
            }
        }
    }


}
